package com.tanqidi.arcgisandroid.ui.projectConfig.exportData

import android.os.Environment
import android.text.TextUtils
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.esri.arcgisruntime.concurrent.ListenableFuture
import com.esri.arcgisruntime.data.*
import com.esri.arcgisruntime.geometry.GeometryType
import com.esri.arcgisruntime.geometry.Point
import com.esri.arcgisruntime.geometry.Polygon
import com.esri.arcgisruntime.geometry.Polyline
import com.tanqidi.arcgisandroid.R
import com.tanqidi.arcgisandroid.base.diyJetpack.BusMutableLiveData
import com.tanqidi.arcgisandroid.base.utils.FileUtil
import com.tanqidi.arcgisandroid.base.utils.getString
import com.tanqidi.arcgisandroid.base.utils.showToast
import com.tanqidi.arcgisandroid.data.project.Project
import com.tanqidi.arcgisandroid.data.project.ProjectRepository
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.gdal.ogr.Geometry
import org.gdal.ogr.ogr
import java.io.File
import java.io.FileWriter
import java.io.InputStream
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject

@HiltViewModel
class ExportDataViewModel @Inject constructor(
    private val projectRepository: ProjectRepository
) : ViewModel() {

    companion object {
        const val EXPORT_TYPE_KML = "kml"
        const val EXPORT_TYPE_SHP = "shp"
        const val EXPORT_TYPE_EXCEL = "excel"
        const val EXPORT_TYPE_GEODATABASE = "geodatabase"
        const val EXPORT_TYPE_GEOJSON = "geojson"
    }

    val downStatusLiveData = BusMutableLiveData<Boolean>()

    fun getProjectByName(name:String) : Project {
        return projectRepository.getProjectByName(name)
    }

    fun export(featureTable: FeatureTable, layerName: String, exportType: String) {
        val param = QueryParameters().apply {
            whereClause = "1=1"
        }
        val queryFeaturesAsync = if(featureTable is ServiceFeatureTable){
            featureTable.queryFeaturesAsync(param, ServiceFeatureTable.QueryFeatureFields.LOAD_ALL)
        } else {
            featureTable.queryFeaturesAsync(param)
        }
        queryFeaturesAsync.addDoneListener {
            val featureList = queryFeaturesAsync.get().map { it }
            if(featureList.isEmpty()){
                showToast("无要素")
                downStatusLiveData.postValue(true)
                return@addDoneListener
            }
            when(exportType){
                EXPORT_TYPE_KML -> exportkml(featureTable, layerName, featureList)
                EXPORT_TYPE_EXCEL -> exportExcel(featureTable, layerName)
            }
        }

    }

    private fun exportkml(table: FeatureTable, layerName:String, featureList:List<Feature>) {
        val format = SimpleDateFormat("yyyy年MM月dd日")
        val currentDate = format.format(Date())
        val dirPath = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.export) + File.separator + layerName + File.separator + currentDate + File.separator + "kml"
        val dirFile = File(dirPath)
        if(dirFile.exists()){
            FileUtil.deleteFile(dirFile)
        }
        dirFile.mkdirs()

        val kmlBuffer = StringBuffer()
        kmlBuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
        kmlBuffer.append("<kml xmlns=\"http://www.opengis.net/kml/2.2\">")
        kmlBuffer.append("<Document>")

        val aliasFieldMap = mutableMapOf<String,String>()
        table.fields.forEachIndexed { index, field ->
            aliasFieldMap[field.name] = field.alias
        }
        //过滤一下，有些字段是不需要显示出来的
        val showFieldList = table.fields.filter { it.isEditable }.map { it.name }.toMutableList()
        //循环生成Placemark
        featureList.forEachIndexed { index, feature ->
            try {
                val placemarkBuffer = StringBuffer()
                placemarkBuffer.append("<Placemark>")

                //如果选择了标注信息
                /*if(annotationField != null && !TextUtils.isEmpty(annotationField)){
                    val value = feature.attributes[annotationField]
                    if(value != null && !TextUtils.isEmpty(value.toString())){
                        placemarkBuffer.append("<name>${value}</name>")
                    }
                }*/

                //生成键值对数据
                placemarkBuffer.append("<ExtendedData>")
                feature.attributes.forEach {
                    val key = it.key
                    val value = it.value?.toString() ?: ""
                    //如果是一些globalId之类的，是不要显示出来的
                    if(showFieldList.contains(key)){
                        //如果不是空的就添加，是空的就不加进去了
                        if(!TextUtils.isEmpty(value)){
                            placemarkBuffer.append("<Data name=\"$key\">")
                            placemarkBuffer.append("<value>$value</value>")
                            placemarkBuffer.append("</Data>")
                        }

                    }
                }
                placemarkBuffer.append("</ExtendedData>")

                //转换一下，变成kml数据的坐标
                val geometryType = table.geometryType
                //转换一下gdal的geometry类型
                val gdalGeometryType = if(geometryType == GeometryType.POINT){
                    ogr.wkbPoint
                } else if (geometryType == GeometryType.POLYLINE){
                    ogr.wkbLineString
                } else if (geometryType == GeometryType.POLYGON){
                    ogr.wkbPolygon
                } else {
                    ogr.wkbUnknown
                }

                val geometry = feature.geometry
                val gdalGeometry = Geometry(gdalGeometryType)
                if(geometry is Point){
                    gdalGeometry.AddPoint(geometry.x, geometry.y)
                } else if (geometry is Polyline){
                    geometry.parts.forEachIndexed { index, immutablePart ->
                        immutablePart.points.forEachIndexed { index, point ->
                            gdalGeometry.AddPoint_2D(point.x,point.y)
                        }
                    }
                } else if (geometry is Polygon){
                    val ring = Geometry(ogr.wkbLinearRing)
                    geometry.parts.forEachIndexed { index, immutablePart ->
                        immutablePart.points.forEachIndexed { index, point ->
                            ring.AddPoint_2D(point.x, point.y)
                        }
                    }
                    gdalGeometry.AddGeometry(ring)
                    //如果没有它是个严重的错误，polygon没有封闭点，导出的kml结果就是永远都会少一个封闭坐标点少一条线的边
                    gdalGeometry.CloseRings()
                }
                //讲kml格式的字符串拼接进来
                val exportToKML = gdalGeometry.ExportToKML()
                placemarkBuffer.append(exportToKML)
                placemarkBuffer.append("</Placemark>")
                //将节点添加进来kmlBuffer
                kmlBuffer.append(placemarkBuffer.toString())
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        kmlBuffer.append("</Document>")
        kmlBuffer.append("</kml>")

        //写入文件
        val kmlFile = File(dirPath, "$layerName.kml")
        kmlFile.createNewFile()
        val fileWriter = FileWriter(kmlFile, false)
        fileWriter.write(kmlBuffer.toString())
        fileWriter.close()
        viewModelScope.launch {
            showToast("成功导出${featureList.size}个要素")
            //下载
            val stackList = Stack<Feature>().apply {
                addAll(featureList)
            }
            downImgByFeature(table, stackList, layerName)
        }
    }

    private fun exportExcel(featureTable: FeatureTable, layerName:String) {

    }

    //下载图片
    private fun downImgByFeature(table: FeatureTable, stack: Stack<Feature>, layerName:String) {
        val arcgisFeature = stack.pop() as ArcGISFeature
        val canEditAttachments = arcgisFeature.canEditAttachments()
        if(!canEditAttachments){
            showToast("当前要素不支持附件操作")
            downStatusLiveData.postValue(true)
            return
        }
        if(stack.size == 0){
            showToast("图片导出成功")
            downStatusLiveData.postValue(true)
            return
        }
        //objectId,默认用它作为导出图片的目录，如果配置了图片命名格式，就用字段格式的值作为文件的目录名字
        val oid = table.fields.filter { it.fieldType == Field.Type.OID }[0]
        val attributes = arcgisFeature.attributes
        val pictureDir = attributes?.get(oid.name)?.toString()

        val fetchAttachmentsAsync = arcgisFeature.fetchAttachmentsAsync()
        fetchAttachmentsAsync.addDoneListener {
            val attachmentList = fetchAttachmentsAsync.get()
            //如果是空的？就把目录删除的了
            if(attachmentList.size == 0) {
                //自己调用自己
                downImgByFeature(table, stack, layerName)
                return@addDoneListener
            }
            val jobList = mutableListOf<Single<Any>>()
            //如果要素id目录已经存在了，先删除，保证每次都是新的
            val dirPath = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.feature_img) + File.separator + layerName + File.separator + pictureDir
            val file = File(dirPath)
            if(file.exists()){
                file.delete()
            }

            attachmentList.forEachIndexed { index, attachment ->
                val job = Single.create<Any> { emitter ->
                    val fetchDataAsync = attachment.fetchDataAsync()
                    fetchDataAsync.addDoneListener {
                        val inputStream = fetchDataAsync.get()
                        val fileName = attachment.name
                        writeToLocal(layerName, pictureDir!!, fileName, inputStream)
                        emitter.onSuccess(true)
                    }
                }
                jobList.add(job)
            }
            //等待
            Single.zip(jobList) { objects -> objects }.subscribe({
                //自己调用自己
                downImgByFeature(table, stack, layerName)
            }, {
                it.message?.let { it1 -> Log.e("ERROR =>", it1) }
                //重新添加进来
                stack.add(arcgisFeature)

                //自己调用自己
                downImgByFeature(table, stack, layerName)
            })
        }
    }

    private fun writeToLocal(layerName: String,pictureDir:String, pictureName: String, input: InputStream) {
        val dirPath = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.feature_img) + File.separator + layerName + File.separator + pictureDir
        val file = File(dirPath)
        file.mkdirs()
        FileUtil.write2SDFromInput(dirPath, pictureName, input)
    }
}